#include "cdirect3d.h"
#include <direct.h>
#include <string>
#include <d3dcompiler.h>
#include "../win32/windowsutils.h"
#include "crenderpass.h"

namespace NEngine {

// ------------------------------------------------------------------------------------------------
// Helper function for asserting on direct3d operations
// ------------------------------------------------------------------------------------------------
inline static bool CheckResult(HRESULT hr) {
    if(FAILED(hr)) {
        ASSERT_(0);
        return true;
    }
    
    return false;
}

//------------------------------------------------------------------------------------------------
// Helper for compiling shaders with D3DX11
//------------------------------------------------------------------------------------------------
bool CDirect3D::CompileShaderFromFile(LPCWSTR filename, LPCSTR entrypoint, 
                              LPCSTR shadermodel, ID3DBlob** blobout) {
    std::wstring path = GetEnvironmentVariable("spath");
    path += L"\\*.fx";
    CDynamicArray<std::wstring> files = GetFilesInPath(path, true);

    DWORD shaderflags = D3DCOMPILE_ENABLE_STRICTNESS;
#if defined(DEBUG) || defined(_DEBUG)
    // Set the D3DCOMPILE_DEBUG flag to embed debug information in the shaders.
    // Setting this flag improves the shader debugging experience, but still allows 
    // the shaders to be optimized and to run exactly the way they will run in 
    // the release configuration of this program.
    shaderflags |= D3DCOMPILE_DEBUG;
#endif

    ID3DBlob* errblob;
    for(uint i = 0; i < files.GetSize(); ++i) {
        bool compilefail = CheckResult(D3DX11CompileFromFile(files[i].c_str(), 
                                       NULL, NULL, entrypoint, shadermodel, 
    	                               shaderflags, 0, NULL, blobout, &errblob, NULL));
    	if(compilefail) {
    	    if(errblob != NULL)
    	        OutputDebugStringA((char*)errblob->GetBufferPointer());
    	    if(errblob) 
                errblob->Release();
    	    return compilefail;
    	}
    	if(errblob) 
            errblob->Release();
    }
    return S_OK;
}

    
// ------------------------------------------------------------------------------------------------
// Compile all the shaders
// ------------------------------------------------------------------------------------------------
void CDirect3D::CreateShaders() {
    // -- compile the vertex shader from file
    ID3DBlob* vsblob = NULL;
    CompileShaderFromFile(L"firstshader.fx", "VSMain", "vs_4_0", &vsblob);

    // -- create the vertex shader
    CheckResult(device->CreateVertexShader(vsblob->GetBufferPointer(), 
                                           vsblob->GetBufferSize(), NULL, &vtxshader));
    CreateNameAndReference_(vtxshader);

    vsblob = NULL;
    // -- create the vtx shader for the lighting pass
    CheckResult(CompileShaderFromFile(L"firstshader.fx", "VTX", "vs_4_0", &vsblob));

    CheckResult(device->CreateVertexShader(vsblob->GetBufferPointer(), 
                vsblob->GetBufferSize(), NULL, &vtxshdrlightpass));

    CreateNameAndReference_(vtxshdrlightpass);

    // -- define the input layout and create it
    D3D11_INPUT_ELEMENT_DESC layout[] = {
        {"SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
        {"Color", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
        {"Normal", 0, DXGI_FORMAT_R32G32B32_FLOAT, 2, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
    };

    // -- create the input layout
    uint numelems = ARRAYSIZE(layout);
    CheckResult(device->CreateInputLayout(layout, numelems, vsblob->GetBufferPointer(),
        vsblob->GetBufferSize(), &ilayout));
    CreateNameAndReference_(ilayout);

    // -- set the ialayout
    ictx->IASetInputLayout(ilayout);

    // -- compile ps shader from file
    ID3DBlob* psblob = NULL;
    CompileShaderFromFile(L"firstshader.fx", "PSMain", "ps_4_0", &psblob);

    // -- create the pixel shader
    CheckResult(device->CreatePixelShader(psblob->GetBufferPointer(), psblob->GetBufferSize(), 
        NULL, &psshader));
    CreateNameAndReference_(psshader);

    CheckResult(CompileShaderFromFile(L"firstshader.fx", "PS", "ps_4_0", &psblob));

    CheckResult(device->CreatePixelShader(psblob->GetBufferPointer(), psblob->GetBufferSize(), 
        NULL, &psshdrlightpass));
    CreateNameAndReference_(psshdrlightpass);
}

// ------------------------------------------------------------------------------------------------
// Create device, context and swapNchain
// ------------------------------------------------------------------------------------------------
void CDirect3D::CreateDeviceAndSwapChain(const HWND& hwnd, const RECT& rc) {
    uint width = rc.right - rc.left;
    uint height = rc.bottom - rc.top;

    uint createdeviceflags = 0;
    #ifdef _DEBUG
        createdeviceflags |= D3D11_CREATE_DEVICE_DEBUG;
    #endif

    D3D_DRIVER_TYPE drivertypes[] = {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_WARP,
        D3D_DRIVER_TYPE_REFERENCE,
    };

    uint numdrivertypes = ARRAYSIZE(drivertypes);

    D3D_FEATURE_LEVEL featurelevels[] = {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
    };

    uint numfeaturelevels = ARRAYSIZE(featurelevels);

    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = hwnd;
    sd.SampleDesc.Count = 8;
    sd.SampleDesc.Quality = 16;
    sd.Windowed = TRUE;

    D3D_DRIVER_TYPE drivertype = D3D_DRIVER_TYPE_NULL;
    for(uint drivertypeindex = 0; drivertypeindex < numdrivertypes; drivertypeindex++) {
        drivertype = drivertypes[drivertypeindex];
        HRESULT hr = D3D11CreateDeviceAndSwapChain(NULL, drivertype, NULL, createdeviceflags, featurelevels, 
                                                   numfeaturelevels, D3D11_SDK_VERSION, &sd, &swapchain, 
                                                   &device, &supftrlvl, &ictx);

        if(SUCCEEDED(hr)) {
            CreateNameAndReference_(ictx);
            CreateNameAndReference_(device);
            CreateNameAndReference_(swapchain);
            break;
        }
    }
}

// ------------------------------------------------------------------------------------------------
// Create lighting resources
// ------------------------------------------------------------------------------------------------
void CDirect3D::CreateLightingResources(const uint width, const uint height, const uint length) {
    // -- desc for shadow map
    D3D11_TEXTURE2D_DESC desc;
    ZeroMemory(&desc, sizeof(desc));
    desc.Width = width;
    desc.Height = height;
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_R32_FLOAT;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
    desc.CPUAccessFlags = 0;
    desc.MiscFlags = 0;

    ID3D11Texture2D* shadowmap[kMaxConeLights];
    for(uint i = 0; i < length; i++) {
        // -- create texture for lighting pass
    	CheckResult(device->CreateTexture2D(&desc, NULL, &shadowmap[i]));
    	CreateNameAndReference_(shadowmap[i]);

    	// -- create the render target view
    	CheckResult(device->CreateRenderTargetView(shadowmap[i], NULL, &rtviewlighting[i]));
    	CreateNameAndReference_(rtviewlighting[i]);

        // -- create the shader resource view
        CheckResult(device->CreateShaderResourceView(shadowmap[i], NULL, &srviewlighting[i]));
        CreateNameAndReference_(srviewlighting[i]);
    }

    // -- Create depth stencil texture for lighting pass
    ZeroMemory(&desc, sizeof(desc));
    desc.Width = width;
    desc.Height = height;
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    desc.CPUAccessFlags = 0;
    desc.MiscFlags = 0;

    // Create the depth stencil view
    D3D11_DEPTH_STENCIL_VIEW_DESC descdsv;
    ZeroMemory(&descdsv, sizeof(descdsv));
    descdsv.Format = descdsv.Format;
    descdsv.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS;
    descdsv.Texture2D.MipSlice = 0;
 
    ID3D11Texture2D* dstencillighting[kMaxConeLights];

    for(uint i = 0; i < length; ++i) {
        CheckResult(device->CreateTexture2D(&desc, NULL, &dstencillighting[i]));
    	CreateNameAndReference_(dstencillighting[i]);

    	CheckResult(device->CreateDepthStencilView(dstencillighting[i], &descdsv, &dsviewlighting[i]));
    	CreateNameAndReference_(dsviewlighting[i]);
    }
}

// ------------------------------------------------------------------------------------------------
// Create the the Views, Samplers, and stencils
// ------------------------------------------------------------------------------------------------
void CDirect3D::CreateViewsAndSamplers(const RECT& rc, bool wireframe, const uint length) {
    uint width = rc.right - rc.left;
    uint height = rc.bottom - rc.top;

    // -- create the buffer
    ID3D11Texture2D* swapbuffer = NULL;
    CheckResult(swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&swapbuffer));
    CreateNameAndReference_(swapbuffer);

    // -- create render target view
    CheckResult(device->CreateRenderTargetView(swapbuffer, NULL, &rtview));
    CreateNameAndReference_(rtview);

    // Create the sample state
    D3D11_SAMPLER_DESC sampdesc;
    ZeroMemory(&sampdesc, sizeof(sampdesc));
    sampdesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampdesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
    sampdesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
    sampdesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
    sampdesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampdesc.MinLOD = 0;
    sampdesc.MaxLOD = D3D11_FLOAT32_MAX;
    CheckResult(device->CreateSamplerState(&sampdesc, &sampler));
    CreateNameAndReference_(sampler);

    ictx->PSSetSamplers(0, 1, &sampler);

    // -- create rasterizer desc
    D3D11_RASTERIZER_DESC rd;
    rd.AntialiasedLineEnable = TRUE;
    rd.CullMode = D3D11_CULL_BACK;
    rd.DepthBias = 0;
    rd.DepthBiasClamp = 0.0f;
    rd.DepthClipEnable = TRUE;

    if(wireframe)
        rd.FillMode = D3D11_FILL_WIREFRAME;
    else
        rd.FillMode = D3D11_FILL_SOLID;
    rd.FrontCounterClockwise = TRUE;
    rd.MultisampleEnable = TRUE;
    rd.ScissorEnable = FALSE;
    rd.SlopeScaledDepthBias = 0.0f;

    ID3D11RasterizerState* rstate;
    CheckResult(device->CreateRasterizerState(&rd, &rstate));
    CreateNameAndReference_(rstate);

    ictx->RSSetState(rstate);

    ID3D11Texture2D* dstencil;

    // -- Create depth stencil texture
    D3D11_TEXTURE2D_DESC desc;
    ZeroMemory(&desc, sizeof(desc));
    ZeroMemory(&dstencil, sizeof(dstencil));
    desc.Width = width;
    desc.Height = height;
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    desc.SampleDesc.Count = 8;
    desc.SampleDesc.Quality = 16;
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    desc.CPUAccessFlags = 0;
    desc.MiscFlags = 0;
    CheckResult(device->CreateTexture2D(&desc, NULL, &dstencil));
    CreateNameAndReference_(dstencil);

    // Create the depth stencil view
    D3D11_DEPTH_STENCIL_VIEW_DESC descdsv;
    ZeroMemory(&descdsv, sizeof(descdsv));
    descdsv.Format = descdsv.Format;
    descdsv.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS;
    descdsv.Texture2D.MipSlice = 0;
    CheckResult(device->CreateDepthStencilView(dstencil, &descdsv, &dsview));
    CreateNameAndReference_(dsview);

    // -- setup the viewport
    D3D11_VIEWPORT vp;
    vp.Width = (FLOAT)width;
    vp.Height = (FLOAT)height;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    ictx->RSSetViewports(1, &vp);

    // -- create lighting resources and views
    CreateLightingResources(width, height, length);
}

// ------------------------------------------------------------------------------------------------
// Create the vertex, index and normal buffers
// ------------------------------------------------------------------------------------------------
void CDirect3D::CreateBuffers(const RECT& rc) {
    uint width = rc.right - rc.left;
    uint height = rc.bottom - rc.top;

    // -- create the vtx buffer
    D3D11_BUFFER_DESC bd;
    ZeroMemory(&bd, sizeof(bd));
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(Vector4) * objects.vertices.GetSize();
    bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = 0;
    D3D11_SUBRESOURCE_DATA initdata;
    ZeroMemory(&initdata, sizeof(initdata));
    initdata.pSysMem = &objects.vertices[0];
    CheckResult(device->CreateBuffer(&bd, &initdata, &vtxbuffer));
    CreateNameAndReference_(vtxbuffer);

    // -- set the input assembler vtx buffer
    uint stride = sizeof(Vector4);
    uint offset = 0;
    ictx->IASetVertexBuffers(0, 1, &vtxbuffer, &stride, &offset);

    // -- create the clr buffer
    ZeroMemory(&bd, sizeof(bd));
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(Vector4) * objects.colors.GetSize();
    bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = 0;
    ZeroMemory(&initdata, sizeof(initdata));
    initdata.pSysMem = &objects.colors[0];
    CheckResult(device->CreateBuffer(&bd, &initdata, &clrbuffer));
    CreateNameAndReference_(clrbuffer);

    // -- set the input assembler clr buffer
    ictx->IASetVertexBuffers(1, 1, &clrbuffer, &stride, &offset);

    // -- create the norm buffer
    ZeroMemory(&bd, sizeof(bd));
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(Vector4) * objects.normals.GetSize();
    bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = 0;
    ZeroMemory(&initdata, sizeof(initdata));
    initdata.pSysMem = &objects.normals[0];
    CheckResult(device->CreateBuffer(&bd, &initdata, &nrmbuffer));
    CreateNameAndReference_(nrmbuffer);

    // -- set the input assembler norm buffer
    ictx->IASetVertexBuffers(2, 1, &nrmbuffer, &stride, &offset);

    // Set primitive topology
    ictx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    XMMATRIX proj = XMMatrixPerspectiveFovRH(XM_PIDIV4, width/(FLOAT)height, 0.1f, 100000.0f);
    cb.proj = XMMatrixTranspose(proj);

    // -- create constant buffer 
    ZeroMemory(&bd, sizeof(bd));
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(SConstantBuffer);
    bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    bd.CPUAccessFlags = 0;
    ZeroMemory(&initdata, sizeof(initdata));
    initdata.pSysMem = &cb;
    CheckResult(device->CreateBuffer(&bd, &initdata, &cbuffmats));
    CreateNameAndReference_(cbuffmats);

    // -- set the constant buffer to the vertex shader
    ictx->VSSetConstantBuffers(0, 1, &cbuffmats);

    ZeroMemory(&initdata, sizeof(initdata));
    ZeroMemory(&bd, sizeof(bd));
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(CBuildLights::SLights);
    bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    bd.CPUAccessFlags = 0;
    ZeroMemory(&initdata, sizeof(initdata));
    initdata.pSysMem = &clightbuff;

    // -- create the light buffers
    CheckResult(device->CreateBuffer(&bd, &initdata, &bufflights));
    CreateNameAndReference_(bufflights);

    // -- set the constant buffer lights to the pixel shader
    ictx->PSSetConstantBuffers(0, 1, &bufflights);
}

// ------------------------------------------------------------------------------------------------
// Create lighting pass
// ------------------------------------------------------------------------------------------------
void CDirect3D::CreateRenderPassLighting(const uint length) {
    CRenderPass::SPassRef rpasses[kMaxConeLights];
    for(uint i = 0; i < length; ++i) {
        rpasses[i].device = &device;
    	rpasses[i].ictx = &ictx;
    	rpasses[i].rt = &rtviewlighting[i];
    	rpasses[i].dstencil = &dsviewlighting[i];
    	rpasses[i].vtxshader = &vtxshdrlightpass;
    	rpasses[i].psshader = &psshdrlightpass;
    	rpasses[i].cb = &cb;
    	rpasses[i].cbuffmats = &cbuffmats;
    	rpasses[i].objects = &objects;
    	rpasses[i].view = &lightviews[i];
    	rpasses[i].numsres = 0;
    	rpasses[i].sres = NULL;
    	renderpasses.Grow(rpasses[i]);
    }
}

// ------------------------------------------------------------------------------------------------
// Create render pass
// ------------------------------------------------------------------------------------------------
void CDirect3D::CreateRenderPass(const uint length) {
    CRenderPass::SPassRef rpass;
    rpass.device = &device;
    rpass.ictx = &ictx;
    rpass.rt = &rtview;
    rpass.dstencil = &dsview;
    rpass.vtxshader = &vtxshader;
    rpass.psshader = &psshader;
    rpass.cb = &cb;
    rpass.cbuffmats = &cbuffmats;
    rpass.objects = &objects;
    rpass.view = curcam;
    rpass.numsres = length;
    rpass.sres = srviewlighting;
    renderpasses.Grow(rpass);
}

// ------------------------------------------------------------------------------------------------
// Create Render passes
// ------------------------------------------------------------------------------------------------
void CDirect3D::CreateRenderPasses(const uint length) {
    CreateRenderPassLighting(length);
    CreateRenderPass(length);
}

// ================================================================================================
// Initialize the direct3d environment
// ================================================================================================
void CDirect3D::Init(CImportObject::SObject& objects_, 
                     const CBuildLights::SLights& cbufflights_, 
                     CCamera*& curcam_,
                     const CDynamicArray<CCamera*>& lviews_, 
                     const HWND hwnd, const RECT& rc, bool wireframe) {
    // -- assign the objects
    objects = objects_;
    clightbuff = cbufflights_;
    lightviews = lviews_;
    curcam = &curcam_;

    CreateDeviceAndSwapChain(hwnd, rc);
    CreateViewsAndSamplers(rc, wireframe, lightviews.GetSize());
    CreateShaders();
    CreateBuffers(rc);
    CreateRenderPasses(lightviews.GetSize());
}

// ================================================================================================
// Update the scene
// ================================================================================================
void CDirect3D::Update() {
    // -- update the cone light direction
    for(uint i = 0; i < lightviews.GetSize(); ++i) {
        const Matrix4x4& lightwtrans = lightviews[i]->GetWorldTransform();
        float intense = clightbuff.cones[i].sptlightdir.w;
        float clr = clightbuff.cones[i].sptlightpos.w;
        clightbuff.cones[i].sptlightdir = Vector4(0.0f, 0.0f, -1.0f) * lightwtrans.GetRotation();
        clightbuff.cones[i].sptlightpos = lightwtrans.GetTranslation().GetRow4();
        clightbuff.cones[i].sptlightdir.w = intense;
        clightbuff.cones[i].sptlightpos.w = clr;

        const Matrix4x4& lightvtrans = lightviews[i]->GetViewTransform().Transpose4x4();
        cb.lightview[i] = lightvtrans;
    }

    ictx->UpdateSubresource(bufflights, 0, NULL, &clightbuff, 0, 0);
}

// ------------------------------------------------------------------------------------------------
// Draw the scene
// ------------------------------------------------------------------------------------------------
void CDirect3D::Draw() {
    ushort vtxoffset = 0;
    for(uint j = 0; j < objects.numindices.GetSize(); ++j) {
        const Matrix4x4& world = objects.wmats[j];
        cb.world = world.Transpose4x4();
        cb.tinvworld = world.InvTrans();
        cb.tinvworld = cb.tinvworld.Transpose4x4();

        // -- update matrix buffer per object and draw them
        ictx->UpdateSubresource(cbuffmats, 0, NULL, &cb, 0,0);
        ictx->Draw(objects.numverts[j], vtxoffset);

        // -- find the offset to the next set of vertices
        vtxoffset += objects.numverts[j];
    }
}

// ================================================================================================
// Render function
// ================================================================================================
void CDirect3D::Render() {
    while(!renderpasses.EndOfList())
        renderpasses.GetNext().RenderPass();
    renderpasses.Reset();

    // -- present the back buffer rt
    swapchain->Present(0,0);
}

} // namespace NEngine
